# executor.py
from __future__ import annotations
from typing import Any, Dict, List, Tuple
from pydantic import ValidationError, BaseModel
import time, json, uuid, random

from registry import ToolRegistry
from tools import TransientToolError, FatalToolError

def _now_ms() -> int:
    return int(time.time() * 1000)

def _jsonline(d: Dict[str, Any]) -> str:
    return json.dumps(d, ensure_ascii=False, separators=(",", ":"))

class Executor:
    """统一的“校验-修复-执行-重试-记录”执行器。
    
    新增：
      - 结构化日志（JSON Lines）：每个阶段输出一行 JSON。
      - 指数退避重试（带抖动）：delay = base * 2^(attempt-1) + jitter。
    """
    def __init__(
        self,
        registry: ToolRegistry,
        backoff_base: float = 0.2,   # 秒
        jitter_max: float = 0.15,    # 秒
    ) -> None:
        self.registry = registry
        self.backoff_base = backoff_base
        self.jitter_max = jitter_max

    def execute(self, tool_name: str, arg_dict: Dict[str, Any]) -> Tuple[bool, Any, str]:
        trace_id = str(uuid.uuid4())
        logs: List[str] = []
        t_start = _now_ms()

        def log(event: str, **fields: Any) -> None:
            rec = {
                "ts": _now_ms(),
                "trace_id": trace_id,
                "event": event,
                "tool": tool_name,
            }
            rec.update(fields)
            logs.append(_jsonline(rec))

        try:
            spec = self.registry.get(tool_name)
        except KeyError as e:
            log("router.miss", error=str(e), args=arg_dict)
            return False, str(e), "\n".join(logs)

        log("router.hit", desc=spec.desc, args=arg_dict)

        # 校验
        try:
            params = spec.params_model(**arg_dict)
            log("validate.ok")
        except ValidationError as ve:
            log("validate.fail", errors=ve.errors())
            if spec.repair:
                fixed = spec.repair(arg_dict)
                log("repair.apply", fixed=fixed)
                try:
                    params = spec.params_model(**fixed)
                    arg_dict = fixed
                    log("validate.ok.after_repair")
                except ValidationError as ve2:
                    log("validate.fail.after_repair", errors=ve2.errors())
                    return False, f"参数无效：{ve2.errors()}", "\n".join(logs)
            else:
                return False, f"参数无效：{ve.errors()}", "\n".join(logs)

        # 执行 + 指数退避重试
        attempts = 0
        while True:
            attempts += 1
            try:
                t0 = _now_ms()
                result = spec.func(params)  # type: ignore[arg-type]
                dur_ms = _now_ms() - t0
                log("exec.ok", attempt=attempts, duration_ms=dur_ms, result=result)
                log("trace.close", duration_ms=_now_ms() - t_start)
                return True, result, "\n".join(logs)
            except TransientToolError as te:
                log("exec.retryable", attempt=attempts, error=str(te))
                if attempts > spec.max_retries:
                    log("exec.giveup", attempts=attempts)
                    log("trace.close", duration_ms=_now_ms() - t_start)
                    return False, f"工具暂时不可用：{te}", "\n".join(logs)
                # 指数退避 + 抖动
                delay = self.backoff_base * (2 ** (attempts - 1)) + random.uniform(0, self.jitter_max)
                log("backoff.sleep", delay_s=round(delay, 3))
                time.sleep(delay)
                continue
            except FatalToolError as fe:
                log("exec.fatal", attempt=attempts, error=str(fe))
                log("trace.close", duration_ms=_now_ms() - t_start)
                return False, f"工具执行失败：{fe}", "\n".join(logs)
            except Exception as e:
                log("exec.error", attempt=attempts, error=f"{e.__class__.__name__}: {e}")
                log("trace.close", duration_ms=_now_ms() - t_start)
                return False, f"工具异常：{e}", "\n".join(logs)
